Pi for Breakfast
Table of Contents
Pi For Breakfast #
The first thing anyone thinks when they get a raspberry-pi:
now what?
Casting about for a problem to my solution I’ve been noticing an egregious amount of ads in my feeds lately - lets fix that now! There are many tutorials about install pi-hole - such as the README itself!
I wanted to take this ad-blocking server to the next level!
tl;dr here is the tailscale post on this subject: Access a Pi-hole or Raspberry Pi from anywhere. I’d note you need to disable sudo systemctl disable systemd-resolved.service && sudo systemctl stop systemd-resolved.service
This post dives into hardening and monitoring our dns server as a core component to our home network.
Available Anywhere!! #
The one big drawback to self-hosting anything is that unless you have a static IP from your ISP, chances are once you leave home your access to your local network does to.
To resolve this I created a Tailscale account which is free for personal use - perfect in this case. Tailscale allows you to create a VPN (virtual Private Network) between you and your devices. In this case we’re going to create a “local” network with our raspberry-pi being the DNS server.
Once you create an account run tailscale up
Bootstrap Raspberry Pi #
Because this is going to be our DNS server we want a consistent ip address to reach it. By default your router will assign dynamic IPs over DHCP. To assign your Pi a static ip update this file on the boot sector of the SD card:
/etc/netplan/50-cloud-init.yaml
#network:
# ethernets:
# eth0:
# dhcp4: true
# optional: true
# version: 2
network:
ethernets:
eth0:
addresses:
- 192.168.0.26/24
gateway4: 192.168.0.1
nameservers:
addresses: [127.0.0.53]
optional: true
192.168.0.26/24
is the static IP I want to assign my pi
192.168.0.1
: is the gateway to my local network. You can find this with:
ip a | grep enp9s0
(or eth0
)
grok a line that looks like this:
inet 192.168.0.24/24 brd 192.168.0.255
network:
ethernets:
eth0:
addresses:
- {{ ipv4 address between 192.168.0.1 and 192.168.0.255 }}
gateway4: {{ min ip address of the broadcast ip (192.168.0.0
) would be your router - so you want .1
}}
Plug-in your Pi and clip an ethernet into your router. Since our Pi is headless we need to find it and shell in.
Test with Nmap #
Nmap is an amazing tool for testing networks - we’re going to use it to find our Pi and make sure its configured properly.
sudo nmap -sn 192.168.0.24/24
and we find an entry like this:
Nmap scan report for 192.168.0.26
Host is up (0.00024s latency).
MAC Address: E4:5F:01:6A:62:71 (Raspberry Pi Trading)
once you’re in you’ll want to do the following:
Install Tailscale #
Follow the official docs here for your respective platform (I installed Ubuntu 20.04.3 LTS
).
Once you’ve run tailscale up
note the IP address $TAILSCALE_IP.
Harden SSH Access #
1. update your /etc/ssh/sshd_config
with your static and tailscale IPs
ListenAddress 192.168.0.26
ListenAddress {{ $TAILSCALE_IP }}
this way if the tailscale proc ever crashes you can still SSH into your pi
I’d also recommend banning PasswordAuthenticatio
n` and use your personal SSH key or SSH certificate:
PasswordAuthentication no
You can read more about creating and uploading a personal SSH key here:
run systemctl restart sshd
and verify in another tab that you can connect.
What makes this pi so tasty? #
The official Tailscale + Pi-hole docs use the docker-compose
stack - while this is great for getting started its hard to know other info such as:
- is pi-hole up?
- is it consuming too many resources on my resource-constrained raspberry-pi?
- is it restarting frequently?
- where are my logs?
All of these questions can be solved using an orchestrator. I like using Hashicorp’s simple Nomad offering - its easy to get started and great for my simple homelab use case.
Install Docker #
sudo apt install docker.io
sudo apt install docker-compose
Follow the post-install instructions here:
Install Nomad #
Note we don’t want to use the apt repos here as of this writing they are far behind (0.8.x?).
Here is the nomad.hcl
for running both the server and client on a raspberry-pi with Docker enabled:
nomad@pi-for-breakfast:~/pi-for-breakfast$ cat /etc/nomad.d/nomad.hcl
datacenter = "homelab"
data_dir = "/opt/nomad/data"
# tailscale
bind_addr = "$TAILSCALE_IP" <-- note the "double-quotes"
# equivalent to nomad agent -dev
server {
enabled = true
bootstrap_expect = 1
}
client {
enabled = true
servers = ["$TAILSCALE_IP:4647"]
host_network "tailscale" {
cidr = "$TAILSCALE_IP/30"
interface = "tailscale0"
}
node_class = "raspberry-pi"
}
plugin "docker" {
config {
endpoint = "unix:///var/run/docker.sock"
volumes {
enabled = true
selinuxlabel = "z"
}
allow_privileged = true
allow_caps = ["audit_write", "chown", "dac_override", "fowner", "fsetid", "kill", "mknod", "net_bind_service", "setfcap", "setgid", "setpcap", "setuid", "sys_chroot", "net_admin"]
}
}
note everything should be owned by the
nomad
user
run systemctl restart nomad
and you should be in business.
Download Pi-hole for Docker #
sudo su - nomad
cd /opt
git clone https://github.com/pi-hole/docker-pi-hole.git
This is how we’ll reference the core configs when mounting them into the Docker container.
Create Pi-hole Nomad job #
One note here that since we are advertising our Nomad server stack using a $TAILSCALE_IP we’ll want to add NOMAD_ADDR
to our ~/.bashrc
like this:
# Nomad
export NOMAD_ADDR="http://$TAILSCALE_IP:4646"
for both ubuntu
and nomad
users. Be sure to run source ~/.bashrc
afterwards.
job "pi-hole" {
datacenters = ["homelab"]
type = "system"
constraint {
attribute = "${attr.kernel.name}"
value = "linux"
}
constraint {
attribute = "${node.class}"
value = "raspberry-pi"
}
group "pi-hole" {
network {
mode = "bridge"
port "dhcp" {
static = 67
to = 67
host_network = "tailscale"
}
port "dns" {
static = 53
to = 53
host_network = "tailscale"
}
port "http" {
static = 8080
to = 80
host_network = "tailscale"
}
}
task "server" {
driver = "docker"
config {
image = "pihole/pihole:latest"
ports = [
"dns",
"dhcp",
"http",
]
volumes = [
"/opt/docker-pi-hole/etc-pihole/:/etc/pihole/",
"/opt/docker-pi-hole/etc-dnsmasq.d/:/etc/dnsmasq.d/",
"/opt/docker-pi-hole/var-log/pihole.log:/var/log/pihole.log",
]
cap_add = ["net_admin", "setfcap"]
}
}
}
}
gist: Pi-hole on Nomad
Note I bound the http server to 8080
to reserve 80
for a later reverse proxy like haproxy or envoy.
Submit the job to Nomad with:
nomad run pi-hole.nomad
Configure Pi-hole #
reset the password:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1cdb866fa9a3 pihole/pihole:latest "/s6-init" 2 minutes ago Up 2 minutes (healthy) server-88047994-f8ef-7b60-18c9-cd52dd631107
e9b653a9e628 gcr.io/google_containers/pause-arm64:3.1 "/pause" 2 minutes ago Up 2 minutes nomad_init_88047994-f8ef-7b60-18c9-cd52dd631107
nomad@pi-for-breakfast:~/nomad-jobs$ docker exec -it 1cdb866fa9a3 bash
root@e9b653a9e628:/# pihole -a -p
Enter New Password (Blank for no password):
???
Profit
Store this in your password manager of choice.
Finally login to your Pi-hole admin panel and setup the configuration like so:
Note that the last option should not be used on devices which are directly connected to the Internet. This option is safe if your Pi-hole is located within your local network, i.e. protected behind your router, and you have not forwarded port 53 to this device. In virtually all other cases you have to make sure that your Pi-hole is properly firewalled.
✅ Listen on all interfaces, permit all origins
Note that the last option should not be used on devices which are directly connected to the Internet. This option is safe if your Pi-hole is located within your local network, i.e. protected behind your router, and you have not forwarded port 53 to this device. In virtually all other cases you have to make sure that your Pi-hole is properly firewalled.
I’m pasting in this notice to breakdown why this works here - we are using Tailscale to securely connect to our Raspberry-Pi-backed DNS server on our local network. If this were installed on say a VPS like DigitalOcean or Linode we’d have much bigger problems (my remark earlier about using an SSH key or certificate would be mandatory then).
Make sure you enable DNSSEC (Google, Cloudflare, and Pi-hole support it) We’ll verify that it works once we’ve enabled Tailscale DNS…
Tailscale for Days #
Tailscale has a magical feature that enforces DNS servers across all of your devices connected to the Tailscale VPN - this is a great use-case for a DNS server.
1. Login to your Tailscale Account
2. Select DNS
3. Set the nameserver as $TAILSCALE_IP
4. Enable “Override local DNS” - this will revert your /etc/resolv.conf
see troubleshooting if your DNS fails to resolve.
Testing #
Dig #
Try forcing DNS lookups via your Pi-hole with:
dig @$TAILSCALE_IP +short google.com
if this hangs make sure you have your pi-hole admin panel configured correctly ^
DNSSEC #
Test that its working with DNSSEC Resolver Test. You’ll want to make sure tailscale status
is running on your machine (as well as Pi itself of course) for this to work.
Test Ad-Blocking #
Head over to a site with loads of ads and pull up your network tab - you should see references to NS_LOOKUP
failures which means the DNS server (your Pi)_ can’t resolve the address which is what we want!
Verify with Nmap #
Just as we used Nmap to start this project we’ll use Nmap to finish it!
Verify what ports are exposed on your local network:
sudo nmap 192.168.0.26
Starting Nmap 7.91 ( https://nmap.org ) at 2021-12-11 17:57 CST
Nmap scan report for 192.168.0.26
Host is up (0.00073s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
22/tcp open ssh
MAC Address: E4:5F:01:6A:62:71 (Raspberry Pi Trading)
Nmap done: 1 IP address (1 host up) scanned in 0.35 seconds
verify that your Tailscale VPN has the expected ports exposed as well:
nmap 100.92.18.10
Starting Nmap 7.91 ( https://nmap.org ) at 2021-12-11 17:53 CST
Nmap scan report for pi-for-breakfast (100.92.18.10)
Host is up (0.059s latency).
Not shown: 997 closed ports
PORT STATE SERVICE
22/tcp open ssh
53/tcp open domain
8080/tcp open http-proxy
Nmap done: 1 IP address (1 host up) scanned in 0.79 seconds
Note that since we’ve bound Nomad to a Tailscale IP that any other services (log shipping, authn, etc) you are running will be available over your Tailscale network. I have plans to exploit this for later projects but might not be ideal for every workload.
Troubleshooting #
One caveat to having Tailscale managing your DNS settings is that during bootstrap (Pi has lost power, Tailscale proc has crashed) we end up in a situation where our devices are depending on our Pi for DNS resolution in addition to itself.
Your very first step when troubleshooting is to dissable “Override local DNS” in your Tailscale account.
Bootstrap Pi-hole Docker images #
failed to setup alloc: pre-run hook "network" failed: failed to create network for alloc: Failed to pull `gcr.io/google_containers/pause-arm64:3.1`: API error (500): Get https://gcr.io/v2/: dial tcp: lookup gcr.io on 100.92.18.10:53: read udp 100.92.18.10:53571->100.92.18.10:53: read: connection refused
temporarilly patch your resolv.conf
cat /etc/resolv.conf
# resolv.conf(5) file generated by tailscale
# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN
# Tailscale
# nameserver 100.92.18.10
# Google
nameserver 8.8.8.8
nameserver 8.8.4.4
sudo systemctl enable systemd-resolved.service && sudo systemctl restart systemd-resolved.service
dig google.com
to verify DNS is now resolving
run nomad run pi-hole.nomad
then enable tailscale “override local DNS”
Run Nomad Client As Root #
failed to setup alloc: pre-run hook "network" failed: failed to configure networking for alloc: failed to initialize table forwarding rules: failed to list iptables chains: running [/usr/sbin/iptables -t filter -S --wait]: exit status 4: Fatal: can't open lock file /run/xtables.lock: Permission denied
update your etc/systemd/system/nomad.service
# Nomad server should be run as the nomad user. Nomad clients
# should be run as root
User=root
Group=nomad
sudo systemctl daemon-reload
sudo systemctl restart nomad
Install CNI Bridge for bridge networking #
failed to setup alloc: pre-run hook "network" failed: failed to configure networking for alloc: failed to configure network: failed to find plugin "bridge" in path [/opt/cni/bin]
wget https://github.com/containernetworking/plugins/releases/download/v1.0.1/cni-plugins-linux-arm64-v1.0.1.tgz
sudo mkdir -p /opt/cni/bin
sudo tar -C /opt/cni/bin -xzf cni-plugins-linux-arm64-v1.0.1.tgz
The benefit of using a bridge
network is that resolvd
systemD can remain running and act as a fallback if tailscale DNS stops working aka your Raspberry Pi gets unplugged :)
Resources #
1 Install Ubuntu On You Raspberry Pi